#region Microsoft Public License
/*
 * (c) Copyright Chenjun Wu. http://utablesdk.codeplex.com
 * 
 * This source is subject to the Microsoft Public License (Ms-PL).
 * Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
 * All other rights reserved.
 */
#endregion

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using UTable.Input;
using UTable.Input.MultiTouch;
using UTable.Objects.Controls.Primitives;
using UTable.Objects.Handlers;

namespace UTable.Objects.Controls
{
    public class USlider: Slider, IControl
    {
        #region Private Fields

        private URepeatButton SliderIncrease;

        private URepeatButton SliderDecrease;

        private UThumb Thumb;

        private int dragFinger = -1;
        private List<int> increaseFingerList = new List<int>();
        private List<int> decreaseFingerList = new List<int>();
        private bool isRepeating = false;
        private bool isIncreasing = true;
        private Dictionary<int, Point> fingerMaps = new Dictionary<int, Point>();
        private Point _thumbPosition;

        #endregion

        #region Constructors

        static USlider()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(USlider), new FrameworkPropertyMetadata(typeof(USlider)));
        }

        public USlider()
        {
            InitializeIControlPart();

            this.InputReceived += inputReceived;
        }

        #endregion

        #region Properties

        public bool IsAnyContactCapturedWithin
        {
            get { return (bool)GetValue(IsAnyContactCapturedWithinProperty); }
            set { SetValue(IsAnyContactCapturedWithinProperty, value); }
        }

        // Using a DependencyProperty as the backing store for IsAnyContactCapturedWithin.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsAnyContactCapturedWithinProperty =
            DependencyProperty.Register("IsAnyContactCapturedWithin", typeof(bool), typeof(USlider), new UIPropertyMetadata(false));

        public Brush GlowBrush
        {
            get { return (Brush)GetValue(GlowBrushProperty); }
            set { SetValue(GlowBrushProperty, value); }
        }

        // Using a DependencyProperty as the backing store for GlowBrush.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GlowBrushProperty =
            DependencyProperty.Register("GlowBrush", typeof(Brush), typeof(USlider), new UIPropertyMetadata(new SolidColorBrush(Colors.White)));

        #endregion

        #region Private Methods

        private void inputReceived(InputEventArgs args)
        {
            if (!this.IsEnabled)
                return;

            if (args is MultiTouchEventArgs)
                OnMultiTouchDetected(args as MultiTouchEventArgs);
            args.Handled = true;
        }

        private void OnMultiTouchDetected(MultiTouchEventArgs args)
        {
            // _multiTouchProcessor.RaiseMultiTouchEvent(args);
            foreach (FingerEventArgs f in args.FingerEvents)
            {
                switch (f.EventType)
                {
                    case FingerEventType.FINGER_DOWN_EVENT:
                        if (OnThumb(f.Position))
                        {
                            if (dragFinger < 0)
                            {
                                // start drag
                                StartDragging(f.FingerID, f.Position);
                            }
                            continue;
                        }

                        // check repeat
                        this.fingerMaps.Add(f.FingerID, f.Position);
                        SliderIncrease.IsAnyContactCapturedWithin = true;
                        if (! this.Thumb.IsDragging)
                        {
                            StartRepeat(f.Position);
                        }
                        break;
                    case FingerEventType.FINGER_MOVE_EVENT:
                        if (f.FingerID == dragFinger)
                        {
                            // drag the thumb
                            double v = 0;
                            if (this.Orientation == Orientation.Horizontal)
                            {
                                if (IsDirectionReversed)
                                    v = (((this.ActualWidth - f.Position.X) - (Thumb.ActualWidth - _thumbPosition.X)) / (this.ActualWidth - this.Thumb.ActualWidth)) * (this.Maximum - this.Minimum) + this.Minimum;
                                else
                                    v = ((f.Position.X - _thumbPosition.X) / (this.ActualWidth - this.Thumb.ActualWidth)) * (this.Maximum - this.Minimum) + this.Minimum;
                            }
                            else
                            {
                                if (IsDirectionReversed)
                                    v = ((f.Position.Y - _thumbPosition.Y) / (this.ActualHeight - this.Thumb.ActualHeight)) * (this.Maximum - this.Minimum) + this.Minimum;
                                else
                                    v = (((this.ActualHeight - f.Position.Y) - (Thumb.ActualHeight - _thumbPosition.Y)) / (this.ActualHeight - this.Thumb.ActualHeight)) * (this.Maximum - this.Minimum) + this.Minimum;
                            }
                            if (v < this.Minimum)
                                v = this.Minimum;
                            if (v > this.Maximum)
                                v = this.Maximum;
                            this.Value = v;
                            continue;
                        }
                        if (this.fingerMaps.ContainsKey(f.FingerID))
                        {
                            // update position
                            fingerMaps[f.FingerID] = f.Position;
                            // check on thumb
                            if (this.isRepeating)
                            {
                                CheckRepeat();
                            }
                        }
                        break;
                    case FingerEventType.FINGER_UP_EVENT:
                        if (f.FingerID == dragFinger)
                        {
                            // stop dragging
                            StopDragging();
                            continue;
                        }
                        if (this.fingerMaps.ContainsKey(f.FingerID))
                        {
                            this.fingerMaps.Remove(f.FingerID);
                            this.increaseFingerList.Remove(f.FingerID);
                            this.decreaseFingerList.Remove(f.FingerID);
                            CheckRepeat();
                            if (fingerMaps.Count == 0)
                            {
                                SliderIncrease.IsAnyContactCapturedWithin = false;
                            }
                        }
                        break;
                    case FingerEventType.FINGER_OUT_EVENT:
                        if (this.fingerMaps.ContainsKey(f.FingerID))
                        {
                            this.fingerMaps.Remove(f.FingerID);
                            this.increaseFingerList.Remove(f.FingerID);
                            this.decreaseFingerList.Remove(f.FingerID);
                            CheckRepeat();
                            if (fingerMaps.Count == 0)
                            {
                                SliderIncrease.IsAnyContactCapturedWithin = false;
                            }
                        }
                        break;
                }
            }
        }

        private void StartDragging(int fid, Point fingerPosition)
        {
            this.CaptureFinger(fid);
            dragFinger = fid;
            Thumb.SetDragging();
            StopRepeat();
            _thumbPosition = this.TranslatePoint(fingerPosition, Thumb);
        }

        private void StopDragging()
        {
            dragFinger = -1;
            Thumb.SetUndragging();
            StartRepeat(new Point (0, 0));
        }

        private void CheckDragging()
        {
            foreach (int fid in fingerMaps.Keys)
            {
                if (OnThumb(fingerMaps[fid]))
                {
                    StartDragging(fid, fingerMaps[fid]);
                    return;
                }
            }
        }

        private void StartRepeat(Point p)
        {
            if (this.fingerMaps.Count == 0)
                return;
            isRepeating = true;
            increaseFingerList.Clear();
            decreaseFingerList.Clear();
            double horiPos, vertPos;
            if (IsDirectionReversed)
            {
                horiPos = this.Width - (this.Value - this.Minimum) / (this.Maximum - this.Minimum) * (this.ActualWidth - this.Thumb.ActualWidth);
                vertPos = (this.Value - this.Minimum) / (this.Maximum - this.Minimum) * (this.ActualHeight - this.Thumb.ActualHeight);
            }
            else
            {
                horiPos = (this.Value - this.Minimum) / (this.Maximum - this.Minimum) * (this.ActualWidth - this.Thumb.ActualWidth);
                vertPos = this.Height - (this.Value - this.Minimum) / (this.Maximum - this.Minimum) * (this.ActualHeight - this.Thumb.ActualHeight);
            }
            foreach (int fid in fingerMaps.Keys)
            {
                if (this.Orientation == Orientation.Horizontal && fingerMaps[fid].X < horiPos || this.Orientation == Orientation.Vertical && fingerMaps[fid].Y < vertPos)
                {
                    decreaseFingerList.Add(fid);
                }
                else
                    increaseFingerList.Add(fid);
            }
            if ((this.Orientation == Orientation.Horizontal && p.X < horiPos || this.Orientation == Orientation.Vertical && p.Y < vertPos || increaseFingerList.Count == 0) 
                && decreaseFingerList.Count > 0)
            {
                isIncreasing = false;
                SliderIncrease.EndClick();
                SliderDecrease.StartClick();
            }
            else
            {
                isIncreasing = true;
                SliderDecrease.EndClick();
                SliderIncrease.StartClick();
            }
        }

        private void StopRepeat()
        {
            isRepeating = false;
            SliderIncrease.EndClick();
            SliderDecrease.EndClick();
        }

        private void CheckRepeat()
        {
            if (this.fingerMaps.Count == 0)
            {
                StopRepeat();
                return;
            }
            if (isIncreasing && increaseFingerList.Count == 0)
            {
                isIncreasing = false;
                SliderIncrease.EndClick();
                SliderDecrease.StartClick();
            }
            else if (!isIncreasing && decreaseFingerList.Count == 0)
            {
                isIncreasing = true;
                SliderDecrease.EndClick();
                SliderDecrease.StartClick();
            }

            double horiPos, vertPos;
            if (IsDirectionReversed)
            {
                horiPos = this.Width - (this.Value - this.Minimum) / (this.Maximum - this.Minimum) * (this.ActualWidth - this.Thumb.ActualWidth);
                vertPos = (this.Value - this.Minimum) / (this.Maximum - this.Minimum) * (this.ActualHeight - this.Thumb.ActualHeight);
            }
            else
            {
                horiPos = (this.Value - this.Minimum) / (this.Maximum - this.Minimum) * (this.ActualWidth - this.Thumb.ActualWidth);
                vertPos = this.Height - (this.Value - this.Minimum) / (this.Maximum - this.Minimum) * (this.ActualHeight - this.Thumb.ActualHeight);
            }
            int dragf = -1;
            if (isIncreasing)
            {
                foreach (int fid in increaseFingerList)
                {
                    if (this.Orientation == Orientation.Horizontal)
                    {
                        if (fingerMaps[fid].X <= horiPos)
                        {
                            StartDragging(fid, fingerMaps[fid]);
                            dragf = fid;
                            break;
                        }
                    }
                    else
                    {
                        if (fingerMaps[fid].Y <= vertPos)
                        {
                            StartDragging(fid, fingerMaps[fid]);
                            dragf = fid;
                            break;
                        }
                    }
                }
            }
            else
            {
                foreach (int fid in decreaseFingerList)
                {
                    if (this.Orientation == Orientation.Horizontal)
                    {
                        if (fingerMaps[fid].X >= horiPos)
                        {
                            StartDragging(fid, fingerMaps[fid]);
                            dragf = fid;
                            break;
                        }
                    }
                    else
                    {
                        if (fingerMaps[fid].Y >= vertPos)
                        {
                            StartDragging(fid, fingerMaps[fid]);
                            dragf = fid;
                            break;
                        }
                    }
                }
            }
            if (dragf >= 0)
            {
                this.fingerMaps.Remove(dragf);
                if (fingerMaps.Count == 0)
                {
                    SliderIncrease.IsAnyContactCapturedWithin = false;
                }
            }
        }

        private bool OnThumb(Point p)
        {
            Point tp = this.TranslatePoint(p, Thumb);
            if (tp.X >= 0 && tp.X <= Thumb.ActualWidth && tp.Y >= 0 && tp.Y <= Thumb.ActualHeight)
                return true;
            return false;
        }

        private bool OnSlideIncrease(Point p)
        {
            if (OnThumb(p))
                return false;
            if (p.X < 0 || p.X > this.ActualWidth || p.Y < 0 || p.Y > this.ActualHeight)
                return false;
            Point tp = this.TranslatePoint(p, Thumb);
            if (this.Orientation == Orientation.Horizontal && tp.X > 0)
                return true;
            if (this.Orientation == Orientation.Vertical && tp.Y > 0)
                return true;
            return false;

        }
        private bool OnSlideDecrease(Point p)
        {
            if (OnThumb(p))
                return false;
            if (p.X < 0 || p.X > this.ActualWidth || p.Y < 0 || p.Y > this.ActualHeight)
                return false;
            Point tp = this.TranslatePoint(p, Thumb);
            if (this.Orientation == Orientation.Horizontal && tp.X < 0)
                return true;
            if (this.Orientation == Orientation.Vertical && tp.Y < 0)
                return true;
            return false;
        }

        protected override void OnValueChanged(double oldValue, double newValue)
        {
            base.OnValueChanged(oldValue, newValue);
            if (isRepeating)
                CheckRepeat();
        }

		public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();
			Thumb = this.GetTemplateChild("Thumb") as UThumb;
			SliderIncrease = this.GetTemplateChild("SliderIncrease") as URepeatButton;
			SliderDecrease = this.GetTemplateChild("SliderDecrease") as URepeatButton;

			Binding glowBinding = new Binding("GlowBrush");
			glowBinding.Source = this;
			Thumb.SetBinding(UThumb.GlowBrushProperty, glowBinding);
		}

        #endregion

		#region IControl Implementation
		
		#region Private Fields

        private ControlBaseImpl _impl;

        #endregion

		#region Private Methods

		private void InitializeIControlPart()
        {
			_impl = new ControlBaseImpl(this);
            _impl.GotFingerCapture += new UTable.Input.MultiTouch.FingerEventHandler(_impl_GotFingerCapture);
            _impl.LostFingerCapture += new UTable.Input.MultiTouch.FingerEventHandler(_impl_LostFingerCapture);
			this.InputReceived += new UTable.Input.InputEventHandler(_impl_InputReceived);
        }

        void _impl_InputReceived(UTable.Input.InputEventArgs args)
        {
			if (args is UTable.Input.MultiTouch.MultiTouchEventArgs)
				MultiTouchProcessor.RaiseMultiTouchEvent(args as UTable.Input.MultiTouch.MultiTouchEventArgs);
        }

        void _impl_LostFingerCapture(object sender, UTable.Input.MultiTouch.FingerEventArgs args)
        {
            if (LostFingerCapture != null)
                LostFingerCapture(sender, args);
        }

        void _impl_GotFingerCapture(object sender, UTable.Input.MultiTouch.FingerEventArgs args)
        {
            if (GotFingerCapture != null)
                GotFingerCapture(sender, args);
        }

        #endregion

        #region IControl Members

        public UTable.Objects.Controls.ControlInfo Info
        {
            get { return _impl.Info; }
        }
		
		public UTable.Objects.Handlers.MultiTouchProcessor MultiTouchProcessor 
		{ 
			get { return _impl.MultiTouchProcessor; } 
		}

        #endregion

        #region IVisualNode Members

        public String ID
        {
            get { return _impl.ID; }
        }

        public new double Width
        {
            get { return _impl.Width; }
            set { _impl.Width = value; }
        }

        public new double Height
        {
            get { return _impl.Height; }
            set { _impl.Height = value; }
        }

        public new bool IsHitTestVisible
        {
            get { return _impl.IsHitTestVisible; }
            set { _impl.IsHitTestVisible = value; }
        }

        #endregion

        #region IInputNode Members

        public void RaiseEvent(UTable.Input.InputEventArgs e)
        {
            if (InputReceived != null)
            {
                InputReceived(e);
            }
            OnInputReceived(e);
        }

        public virtual void OnInputReceived(UTable.Input.InputEventArgs args)
        {
            _impl.OnInputReceived(args);
        }

        public bool CaptureFinger(int fingerId)
        {
            return _impl.CaptureFinger(fingerId);
        }

        public bool UncaptureFinger(int fingerId)
        {
            return _impl.UncaptureFinger(fingerId);
        }

        public event UTable.Input.MultiTouch.FingerEventHandler GotFingerCapture;

        public event UTable.Input.MultiTouch.FingerEventHandler LostFingerCapture;

        public event UTable.Input.InputEventHandler InputReceived;

        #endregion
        #endregion
    }
}
